#include "dswupgrade.h"
#include <mtd/mtd-user.h>

#define LINE 1024
#define PARTITION_APP0 "APP0"
#define PARTITION_APP1 "APP1"
#define PARTITION_SYS "LINUX"
#define PARTITION_CFG "MTD"
#define PARTITION_FS "FS"

extern ST_ENV_T m_nEnv;

CDswupgrade::CDswupgrade(LPST_ENV_T pEnv, LPST_FIRMWARE_T pFirmware, char* pMTDDev)
: m_pEnv(pEnv)
, m_thread_id(NULL)
, m_bEndThread(false)
{
	memcpy(&m_nFirmware, pFirmware, sizeof(ST_FIRMWARE_T));
	strcpy(m_sMTD, pMTDDev);
	LOG4CPLUS_DEBUG(log_dsw, "Update partion " << pMTDDev);
	pthread_create(&m_thread_id, NULL, CDswupgrade::cbUpgradeThreadFunc, this);
}

CDswupgrade::~CDswupgrade()
{
	m_bEndThread = true;
	if (m_thread_id)
		pthread_join(m_thread_id, NULL);
}

CDswupgrade* CDswupgrade::createNew(LPST_ENV_T pEnv, LPST_FIRMWARE_T pFirmware)
{
	char smtddev[64];
	if (CDswupgrade::IsValidFirmware(pFirmware, smtddev))
		return new CDswupgrade(pEnv, pFirmware, smtddev);
	LOG4CPLUS_ERROR(log_dsw, "Invalid firmware!");
	return NULL;
}

bool CDswupgrade::IsValidFirmware(LPST_FIRMWARE_T pFirmware, char* pmtddev)
{
	FILE* pfd;
	char sbuf[256];
	bool bret = false;
	char* pPartitionName;
	ST_UPGRADE_FILE_HEADER_T nHeader;

#if 0
	pfd = fopen(pFirmware->sFileName, "r");
	if (pfd == NULL){
		LOG4CPLUS_ERROR(log_dsw, "Can't open file " << pFirmware->sFileName << " !");
		return false;
	}
	if (fread(&nHeader, 1, sizeof(nHeader), pfd) != sizeof(nHeader)){
		LOG4CPLUS_ERROR(log_dsw, "Read file " << pFirmware->sFileName << " error!");
		fclose(pfd);
		return false;
	}
	fclose(pfd);
#else
	memcpy(&nHeader, pFirmware->pUploadData, sizeof(nHeader));
#endif
	
	switch((EN_FIRMWARE_TYPE)nHeader.nFileType){
	case FIRMWARE_APP:
		if (strcmp(m_nEnv.sMtd, "/dev/mtd4") == 0)
			pPartitionName = (char*)PARTITION_APP1;
		else
			pPartitionName = (char*)PARTITION_APP0;
		break;
	case FIRMWARE_CFG:
		pPartitionName = (char*)PARTITION_CFG;
		break;
	case FIRMWARE_SYS:
		pPartitionName = (char*)PARTITION_SYS;
		break;
	case FIRMWARE_FS:
		pPartitionName = (char*)PARTITION_FS;
		break;
	default:
		LOG4CPLUS_ERROR(log_dsw, "Unsupport file type!");
		return false;
		break;
	}
	system("cat /proc/mtd > /tmp/mtdinfo");
	pfd = fopen("/tmp/mtdinfo", "r");
	if (pfd  >= 0){
		while(fgets(sbuf, LINE, pfd)){
			if (strstr(sbuf, pPartitionName) == NULL)
				continue;
			else{
				char* pPos;	//mtd0: 00800000 00020000 "Linux"
				pPos = strstr(sbuf, ":");
				if (pPos != NULL){
					char stmp[32];
					int npartitionsize, nerasesize;
					memcpy(stmp, sbuf, pPos - sbuf);
					stmp[pPos - sbuf] = '\0';
					sprintf(pmtddev, "/dev/%s", stmp);
					pPos += 2;
					sscanf(pPos, "%X %X", &npartitionsize, &nerasesize);
					if (nHeader.nFileLen <= npartitionsize)
						bret = true;
				}
				break;
			}
		}
		fclose(pfd);
	}
	remove("/tmp/mtdinfo");
	return bret;
}

bool CDswupgrade::FlashEraseBlock(int ndev, unsigned long nblockaddr, unsigned long nerasesize)
{
	struct erase_info_user erase;
	erase.start = nblockaddr;
	erase.length = nerasesize;
	if (ioctl(ndev, MEMERASE, &erase) < 0){
		LOG4CPLUS_ERROR(log_dsw, "Operate mtd MEMERASE failed!");
		return false;
	}
	return true;
}

void CDswupgrade::UpgradeThreadFunc()
{
	LOG4CPLUS_DEBUG(log_dsw, "Start upgrade thread!");
	int ndev = -1;
	struct mtd_info_user mtd;
	int i, nReadLen;
	ST_UPGRADE_FILE_HEADER_T nHeader;

	if ((ndev = open(m_sMTD, O_RDWR | O_SYNC)) < 0) {
		LOG4CPLUS_ERROR(log_dsw, "Open mtd " << m_sMTD << " failed!");
		goto END_ERROR;
	}
	if (ioctl(ndev, MEMGETINFO, &mtd) < 0){
		LOG4CPLUS_ERROR(log_dsw, "Operate mtd MEMGETINFO failed!");
		goto END_ERROR;
	}
	
	for (i = 0; (i < mtd.size) && !m_bEndThread; i += mtd.erasesize){
		if (!FlashEraseBlock(ndev, i, mtd.erasesize))
			goto END_ERROR;
		if (m_nFirmware.pFunc != NULL)
			m_nFirmware.pFunc(STEP_ERASE, ((double)i / mtd.size), m_nFirmware.pParam, m_nFirmware.pCaller);
	}
	lseek(ndev, 0, SEEK_SET);
#if 0
	FILE* pfd;
	pfd = fopen(m_nFirmware.sFileName, "r");
	if (pfd != NULL){
		if (fread(&nHeader, 1, sizeof(nHeader), pfd) != sizeof(nHeader)){
			LOG4CPLUS_ERROR(log_dsw, "Read file " << m_nFirmware.sFileName << " error!");
			fclose(pfd);
			goto END_UPGRADE;
		}
		char* pBuffer = new char[mtd.erasesize];
		while ((nHeader.nFileLen > 0) && !m_bEndThread){
			nReadLen = fread(pBuffer, 1, mtd.erasesize, pfd);
			if (nReadLen <= 0)
				break;
			if (write(ndev, pBuffer, mtd.erasesize) < 0){
				LOG4CPLUS_ERROR(log_dsw, "Operate mtd write failed!");
				break;
			}
			nHeader.nFileLen -= nReadLen;
			if (m_nFirmware.pFunc != NULL)
				m_nFirmware.pFunc(STEP_WRITE, ((double)(nHeader.nFileLen - nHeader.nFileLen) / nHeader.nFileLen), m_nFirmware.pParam, m_nFirmware.pCaller);
		}
		delete pBuffer;
		fclose(pfd);
		if (m_nFirmware.pFunc != NULL)
			m_nFirmware.pFunc(STEP_FINISH, 0, m_nFirmware.pParam, m_nFirmware.pCaller);
	}
	else{
		LOG4CPLUS_ERROR(log_dsw, "Upgrade failed!");
	}
#else
	memcpy(&nHeader, m_nFirmware.pUploadData, sizeof(nHeader));
	char* pBuffer;
	pBuffer = new char[mtd.erasesize];
	char* pPos;
	pPos = (char*)m_nFirmware.pUploadData + sizeof(nHeader);
	while ((nHeader.nFileLen > 0) && !m_bEndThread){
		memcpy(pBuffer, pPos, (mtd.erasesize > nHeader.nFileLen) ? nHeader.nFileLen : mtd.erasesize);
		nReadLen = (mtd.erasesize > nHeader.nFileLen) ? nHeader.nFileLen : mtd.erasesize;
		if (write(ndev, pBuffer, mtd.erasesize) < 0){
			LOG4CPLUS_ERROR(log_dsw, "Operate mtd write failed!");
			break;
		}
		nHeader.nFileLen -= nReadLen;
		pPos += nReadLen;
		if (m_nFirmware.pFunc != NULL)
			m_nFirmware.pFunc(STEP_WRITE, ((double)(nHeader.nFileLen - nHeader.nFileLen) / nHeader.nFileLen), m_nFirmware.pParam, m_nFirmware.pCaller);
	}
	UpdateBootCfg(ndev, &mtd, pBuffer);
	delete pBuffer;
	if (m_nFirmware.pFunc != NULL)
		m_nFirmware.pFunc(STEP_FINISH, 0, m_nFirmware.pParam, m_nFirmware.pCaller);
	goto END_UPGRADE;
#endif
END_ERROR:
	if (m_nFirmware.pFunc != NULL)
		m_nFirmware.pFunc(STEP_ERROR, 0, m_nFirmware.pParam, m_nFirmware.pCaller);
END_UPGRADE:
	if (ndev != -1)
		close(ndev);
	LOG4CPLUS_DEBUG(log_dsw, "Exit upgrade thread!");
}

void CDswupgrade::UpdateBootCfg(int ndev, struct mtd_info_user *pMtd, char* pBuffer)
{
	lseek(ndev, 0, SEEK_SET);
	int nChecksum = 0;
	for (int i = 0; i < pMtd->size; i += pMtd->erasesize){
		int nCount = read(ndev, pBuffer, pMtd->erasesize);
		if (nCount != pMtd->erasesize){
			LOG4CPLUS_ERROR(log_dsw, "Read " << i << " block failed!");
			break;
		}
		while(nCount-- >= 0){
			nChecksum += pBuffer[nCount];
		}
	}
	CMarkup xml;
	if (!xml.Load("/mnt/mtd/appinfo.xml")){
		xml.SetDoc("");
		xml.AddElem("APPINFO");
		xml.IntoElem();
		xml.AddElem("MTDVER");
		xml.AddAttrib("ID", "/dev/mtd4");
		xml.AddAttrib("VER", "0");
		xml.AddAttrib("CRC", "0");
		xml.AddElem("MTDVER");
		xml.AddAttrib("ID", "/dev/mtd5");
		xml.AddAttrib("VER", "0");
		xml.AddAttrib("CRC", "0");
		xml.OutOfElem();
		xml.Save("/mnt/mtd/appinfo.xml");
		xml.ResetPos();
	}
	int nCurVer = 0;
	xml.FindElem("APPINFO");
	xml.IntoElem();
		while(xml.FindElem("MTDVER")){
			nCurVer = ((atoi(xml.GetAttrib("VER").c_str()) > nCurVer) ? atoi(xml.GetAttrib("VER").c_str()) : nCurVer);
		}
	xml.OutOfElem();
	xml.ResetPos();
	
	xml.FindElem("APPINFO");
	xml.IntoElem();
		while(xml.FindElem("MTDVER")){
			if (strcmp(m_sMTD, xml.GetAttrib("ID").c_str()) == 0){
				int nVer = nCurVer + 1;
				char sVer[32];
				sprintf(sVer, "9%04d", nVer);
				xml.SetAttrib("VER", sVer);
				xml.SetAttrib("CRC", nChecksum);
			}
		}
	xml.OutOfElem();
	xml.Save("/mnt/mtd/appinfo.xml");
}

